﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using log4net;
using System.Reflection;
using System.Security.Cryptography;

namespace EncryptDirectory
{
    public class EncryptionService
    {
        private static readonly ILog _log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.Name);
        private AesManaged _aesManager;
        private string _encryptedFileExtension;

        public EncryptionService(byte[] key, byte[] iv, string encryptedFileExtension)
        {
            _aesManager = new AesManaged();
            _aesManager.Key = key;
            _aesManager.IV = iv;
            _encryptedFileExtension = encryptedFileExtension;
        }

        /// <summary>
        /// Encrypts the files and sub directories of a directory
        /// </summary>
        /// <param name="decryptedDirectory">Directory to Encrypt</param>
        /// <param name="encryptedDirectory">Directory to send the encrypted files to</param>
        public void EncryptDirectory(DirectoryInfo decryptedDirectory, DirectoryInfo encryptedDirectory)
        {
            if (_log.IsDebugEnabled)
                _log.Debug(string.Format("Encrypting from directory: [{0}] to directory [{1}]",
                    decryptedDirectory.FullName,
                    encryptedDirectory.FullName));

            try
            {
                if (encryptedDirectory.Exists)
                    encryptedDirectory.Delete(true);

                encryptedDirectory.Create();

                foreach (var file in decryptedDirectory.GetFiles())
                    EncryptFile(file, encryptedDirectory);

                foreach (var fromSubDirectory in decryptedDirectory.GetDirectories())
                    EncryptDirectory(
                        fromSubDirectory, 
                        new DirectoryInfo(Path.Combine(encryptedDirectory.FullName, fromSubDirectory.Name)));
            }
            catch (Exception e)
            {
                _log.Error(string.Format("Error encrypting directory: {0}", decryptedDirectory.FullName), e);
                throw e;
            }
        }

        /// <summary>
        /// Encrypts a file to a directory
        /// </summary>
        /// <param name="file">File to encrypt</param>
        /// <param name="encryptedDirectory">Directory to send the encrypted file to</param>
        protected void EncryptFile(FileInfo file, DirectoryInfo encryptedDirectory)
        {
            if (_log.IsDebugEnabled) 
                _log.Debug(string.Format("Encrypting File [{0}] to Directory [{1}]", 
                    file.FullName, 
                    encryptedDirectory.FullName));

            string newFilePath = Path.Combine(
                encryptedDirectory.FullName,
                file.Name + _encryptedFileExtension);

            try
            {
                using (var encryptedStream = new CryptoStream(File.Create(newFilePath),_aesManager.CreateEncryptor(),CryptoStreamMode.Write))
                using (var originalFileStream = file.OpenRead())
                {
                    byte[] buffer = new byte[10000];
                    int bufferSize = originalFileStream.Read(buffer, 0, buffer.Length);
                    while (bufferSize > 0)
                    {
                        encryptedStream.Write(buffer, 0, bufferSize);
                        bufferSize = originalFileStream.Read(buffer, 0, buffer.Length);
                    }
                }
            }
            catch (Exception e)
            {
                _log.Error(string.Format("Error encrypting file: {0}", file.FullName), e);
                throw e;
            }
        }

        /// <summary>
        /// Decrypts the files and sub directories of a directory
        /// </summary>
        /// <param name="encryptedDirectory">Encrypted directory to decrypt</param>
        /// <param name="decryptedDirectory">Directory to send the decrypted files to</param>
        public void DecryptDirectory(DirectoryInfo encryptedDirectory, DirectoryInfo decryptedDirectory)
        {
            if (_log.IsDebugEnabled)
                _log.Debug(string.Format("Decrypting from directory: [{0}] to directory [{1}]",
                    encryptedDirectory.FullName,
                    decryptedDirectory.FullName));

            try
            {
                if (decryptedDirectory.Exists)
                    decryptedDirectory.Delete(true);

                decryptedDirectory.Create();

                foreach (var file in encryptedDirectory.GetFiles())
                    DecryptFile(file, decryptedDirectory);

                foreach (var fromSubDirectory in encryptedDirectory.GetDirectories())
                    DecryptDirectory(
                        fromSubDirectory,
                        new DirectoryInfo(Path.Combine(decryptedDirectory.FullName, fromSubDirectory.Name)));
            }
            catch (Exception e)
            {
                _log.Error(string.Format("Error decrypting directory: {0}", encryptedDirectory.FullName), e);
                throw e;
            }
        }

        /// <summary>
        /// Decrypts a file to a directory
        /// </summary>
        /// <param name="file">File to decrypt</param>
        /// <param name="decryptedDirectory">Directory to send the decrypted file to</param>
        protected void DecryptFile(FileInfo file, DirectoryInfo decryptedDirectory)
        {
            if (_log.IsDebugEnabled)
                _log.Debug(string.Format("Decrypting File [{0}] to Directory [{1}]",
                    file.FullName,
                    decryptedDirectory.FullName));

            string decryptedFilePath = (file.Name.EndsWith(_encryptedFileExtension)) ?
                Path.Combine(
                    decryptedDirectory.FullName,
                    file.Name.Substring(0, file.Name.LastIndexOf(_encryptedFileExtension))) :
                Path.Combine(
                    decryptedDirectory.FullName,
                    file.Name);

            try
            {
                using (var decryptStream = new CryptoStream(file.OpenRead(), _aesManager.CreateDecryptor(), CryptoStreamMode.Read))
                using (var decryptedFileStream = File.Create(decryptedFilePath))
                {
                    byte[] buffer = new byte[10000];
                    int bufferSize = decryptStream.Read(buffer, 0, buffer.Length);
                    while (bufferSize > 0)
                    {
                        decryptedFileStream.Write(buffer, 0, bufferSize);
                        bufferSize = decryptStream.Read(buffer, 0, buffer.Length);
                    }
                }
            }
            catch (Exception e)
            {
                _log.Error(string.Format("Error decrypting file: {0}", file.FullName), e);
                throw e;
            }
        }
    }
}
